/**
 * @file Point.h
 * @author Lishijie (lsj1018845759@outlook.com)
 * @brief 
 * @version 0.1
 * @date 2020-11-08
 * 
 * @copyright Copyright (c) 2020
 * 
 */

#ifndef _POINT_H_
#define _POINT_H_

#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
#include <initializer_list>
#include <exception>
#include <math.h>

/**
 * @brief　PointDimensionException类用于判断
 * 声明point<DIM>和用于初始化的数据结构是否有相同的维度；
 */
struct PointDimensionException : public std::logic_error
{
    PointDimensionException(unsigned int d1,unsigned int d2):std::logic_error("Dimension doesn't match!")
    {
        this -> _Dimension_1 = d1;
        this -> _Dimension_2 = d2;
    }
    private:
    unsigned int _Dimension_1;
    unsigned int _Dimension_2;
};

typedef unsigned int Dimension;
typedef double Real;
typedef std::vector<Real> Vector;

#define TEMPLATE template<Dimension DIM>

TEMPLATE
/**
 * @brief 几何点类，生成指定维度的几何点；
 */
class Point
{
public:
    /**
     * @brief Construct a new Point object
     *        生成一个指定维度几何点，各个分量均为０的几何点；
     */
    Point();

    /**
     * @brief Construct a new Point object by initializer_list in std namespace;
     *        生成一个指定维度几何点，各个分量与initializer_list各分量相同的几何点；
     * @param Point_list stl库中的initializer_list,语法为：Point<2> p = {0,0};
     */
    Point(const std::initializer_list<Real> Point_list);

    /**
     * @brief Construct a new Point object by vector in std namespace;
     *        生成一个指定维度几何点，各个分量与vector各分量相同的几何点；
     * @param Point_vector stl库中的vector
     */
    Point(const std::vector<Real> Point_vector);

    /**
     * @brief Construct a new Point object by num array in c++/c;
     *        生成一个指定维度，各个分量与数组各分量相同的几何点；
     * @param array 数组；
     */
    Point(const Real* array);

    /**
     * @brief Destroy the Point object
     */
    ~Point();

    /**
     * @brief 重载下标运算符operator[]，可以用p[0]表示已经声明的几何点的第一个维度；
     * 
     * @param d 指定维度；
     * @return Real& 返回一个实数类型的应用。
     */
    Real& operator[](const Dimension d);

    /**
     * @brief 重载输出运算符operator<<;
     * 
     * @tparam D 该几何点维度；
     * @param os 输出类；
     * @param p 待输出点；
     * @return std::ostream& 输出类的引用；
     */
    template<Dimension D>
    friend std::ostream& operator<<(std::ostream& os,const Point<D> & p);

    /**
     * @brief 重载输入运算符operator>>;
     * 
     * @tparam D 该几何点维度；
     * @param is 输出类；
     * @param p 待输出点；
     * @return std::istream& 输入类的引用；
     */
    template<Dimension D>
    friend std::istream& operator>>(std::istream& is,const Point<D> & p);
    /**
     * @brief 返回两个点在欧几里何空间中的距离；
     * 
     * @tparam D 该几何点维度；
     * @param p1 几何点1;
     * @param p2 几何点2;
     * @return Real 实数，表示距离；
     */
    template<Dimension D>
    friend Real distance(const Point<D> p1,const Point<D> p2);

protected:
    Real _value[DIM];
};

TEMPLATE
Point<DIM>::Point()
{
    std::fill(_value,_value + DIM,static_cast<Real>(0));
};

TEMPLATE
Point<DIM>::Point(const std::initializer_list<Real> Point_list)
{
    if(Point_list.size() != DIM)
    {
        throw PointDimensionException(Point_list.size(),DIM);
    }
    std::initializer_list<Real>::const_iterator i_begin = Point_list.begin();
    std::initializer_list<Real>::const_iterator i_end = Point_list.end();
    std::copy(i_begin,i_end,_value);
};

TEMPLATE
Point<DIM>::Point(const std::vector<Real> Point_vector)
{
    if(Point_vector.size() != DIM)
    {
        throw PointDimensionException(Point_vector.size(),DIM);
    }
    std::vector<Real>::const_iterator i_begin = Point_vector.begin();
    std::vector<Real>::const_iterator i_end = Point_vector.end();
    std::copy(i_begin,i_end,_value);
};

TEMPLATE
Point<DIM>::Point(const Real* array)
{
    std::copy(array,array + DIM,_value);
}

TEMPLATE
Point<DIM>::~Point()
{
}

TEMPLATE
Real& Point<DIM>::operator[](const Dimension d)
{
    return _value[d];
}

template<Dimension D> std::ostream& operator<<(std::ostream& os,const Point<D> & p)
{
    os << "( ";
    for(Dimension d = 0;d < D - 1 ; d++)
    {
        os << p._value[d] << " , ";
    }
    os << p._value[D - 1] << " )" << std::endl;
    return os;
}

template<Dimension D> Real distance(const Point<D> p1,const Point<D> p2)
{
    Real ans;
    for(Dimension d = 0 ;d < D;d++)
        ans += (p1._value[d] - p2._value[d]) * (p1._value[d] - p2._value[d]);
    return sqrt(ans);
}
#undef TEMPLATE
#else 
// DO NOTHING.
#endif
